// Copyright 2016 The Fuchsia Authors
//
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT

#include <lib/unittest/unittest.h>

#include <fbl/alloc_checker.h>
#include <ktl/unique_ptr.h>

#include "tests.h"

static bool alloc_checker_ctor() {
  BEGIN_TEST;

  { fbl::AllocChecker ac; }

  {
    fbl::AllocChecker ac;
    // In some error cases for "new", some versions of C++ say that the
    // allocation function (i.e. the "operator new" overload) is not
    // called, and "new" returns NULL.  So check that ac.check()
    // indicates failure even when AllocChecker's "operator new" wasn't
    // called.
    EXPECT_FALSE(ac.check());
  }

  END_TEST;
}

static bool alloc_checker_basic() {
  BEGIN_TEST;

  fbl::AllocChecker ac;
  ac.arm(8u, true);
  EXPECT_TRUE(ac.check());

  ac.arm(16u, false);
  EXPECT_FALSE(ac.check());

  // Allocating zero bytes, always succeeds.
  ac.arm(0u, false);
  EXPECT_TRUE(ac.check());

  END_TEST;
}

static bool alloc_checker_panic() {
  BEGIN_TEST;
  // Enable any of the blocks below to test the possible panics.

#if 0
    // Arm but not check should panic (true).
    {
        fbl::AllocChecker ac;
        ac.arm(24u, true);
    }
#endif

#if 0
    // Arm but not check should panic (false).
    {
        fbl::AllocChecker ac;
        ac.arm(24u, false);
    }
#endif

#if 0
    // Arming twice without a check should panic.
    {
        fbl::AllocChecker ac;
        ac.arm(24u, true);
        ac.arm(18u, true);
    }
#endif

  END_TEST;
}

struct StructWithCtor {
  char field = 5;
};
static_assert(sizeof(StructWithCtor) == 1, "");

static bool alloc_checker_new() {
  BEGIN_TEST;

  const int kCount = 128;
  fbl::AllocChecker ac;
  ktl::unique_ptr<StructWithCtor[]> arr(new (&ac) StructWithCtor[kCount]);
  EXPECT_EQ(ac.check(), true);

  // Check that the constructor got run.
  for (int i = 0; i < kCount; ++i)
    EXPECT_EQ(arr[i].field, 5);

  END_TEST;
}

static bool alloc_checker_new_fails() {
  BEGIN_TEST;

// When we're in "extreme debug mode" the heap will panic on allocation failure.
#if LK_DEBUGLEVEL <= 2
  // malloc(size_t_max) should fail but currently does not (see
  // ZX-1760), so use large_size instead, because malloc(large_size)
  // does fail.
  size_t size_t_max = ~(size_t)0;
  size_t large_size = size_t_max >> 1;

  // Use a type with a constructor to check that we are not attempting to
  // run the constructor when the allocation fails.
  fbl::AllocChecker ac;
  EXPECT_EQ(new (&ac) StructWithCtor[large_size], nullptr);
  EXPECT_EQ(ac.check(), false);
#endif

  END_TEST;
}

struct LargeStruct {
  uint8_t array[0x1000];
};

static bool test_array_size_overflow_check() {
  BEGIN_TEST;

// When we're in "extreme debug mode" the heap will panic on allocation failure.
#if LK_DEBUGLEVEL <= 2
  size_t size_t_max = ~(size_t)0;
  size_t count = size_t_max / (sizeof(LargeStruct) / 0x10);
  fbl::AllocChecker ac;
  // For this array allocation, the C++ compiler will generate a
  // count*sizeof(LargeStruct) multiplication.  If this overflows, it
  // passes size_t_max to the "operator new()" function.  Check that this
  // allocation fails properly.
  //
  // |count| should be non-const (and non-constexpr) otherwise the
  // compiler may complain at compile time that the program is ill-formed
  // (possibly depending on C++ version).
  //
  // This also requires that AllocChecker's "operator new" override be
  // declared as "noexcept" (which AllocChecker relies on anyway),
  // otherwise the compiler might generate code that raises an exception
  // (again, depending on C++ version).
  EXPECT_EQ(new (&ac) LargeStruct[count], nullptr);
  EXPECT_EQ(ac.check(), false);
#endif

  END_TEST;
}

static bool test_negative_array_size() {
  BEGIN_TEST;

// When we're in "extreme debug mode" the heap will panic on allocation failure.
#if LK_DEBUGLEVEL <= 2
  fbl::AllocChecker ac;

  // Test passing a signed, negative array size.  This should fail
  // safely.
  //
  // C++14 and C++17 say that this size is "erroneous" because "the
  // expression is of non-class type and its value before converting to
  // std::size_t is less than zero".
  //
  // |count| should be non-const (and non-constexpr) otherwise the
  // compiler may complain at compile time that the program is ill-formed
  // (possibly depending on C++ version).
  int count = -1;
  EXPECT_EQ(new (&ac) char[count], nullptr);
  EXPECT_EQ(ac.check(), false);
#endif

  END_TEST;
}

UNITTEST_START_TESTCASE(alloc_checker)
UNITTEST("alloc checker ctor & dtor", alloc_checker_ctor)
UNITTEST("alloc checker basic", alloc_checker_basic)
UNITTEST("alloc checker panic", alloc_checker_panic)
UNITTEST("alloc checker new", alloc_checker_new)
UNITTEST("alloc checker new fails", alloc_checker_new_fails)
UNITTEST("test array size overflow check", test_array_size_overflow_check)
UNITTEST("test negative array size", test_negative_array_size)
UNITTEST_END_TESTCASE(alloc_checker, "alloc_cpp", "Tests of the C++ AllocChecker");
